import cgi
import datetime
import math
import os

from google.appengine.api import users
from google.appengine.api import urlfetch
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db

import router_api

def st():
  import sys, pdb; pdb.Pdb(stdin=sys.__stdin__,stdout=sys.__stdout__).set_trace()

class Router(db.Model):
  # Use who created router and have permission to view it's data
  owner = db.UserProperty(required=True)

  # route info
  address = db.StringProperty(required=True)
  admin_login = db.StringProperty(required=True)
  admin_password = db.StringProperty(required=True)

  # time interval in minutes between data pulls
  pull_interval = db.IntegerProperty(required=True, default=5)

  # some metadata
  created = db.DateTimeProperty(auto_now_add=True)
  modified = db.DateTimeProperty(auto_now=True)

  # status flag
  @property
  def working(self):
    now = datetime.datetime.now()
    snapshot_minute = now.minute / self.pull_interval * self.pull_interval
    snapshot = now - datetime.timedelta(minutes=now.minute - snapshot_minute)
    snapshot_key = snapshot.strftime('%Y%m%d%H%M')
    previous = Metrics.get_by_key_name(snapshot_key, self)
    return previous is not None


class Metrics(db.Model):

  COUNTER_MAX_VALUE = 2**31 - 1

  # Traffic metrics
  # 1. Wifi
  wan_download = db.IntegerProperty()
  wan_download_raw = db.IntegerProperty(default=0)
  wan_upload = db.IntegerProperty()
  wan_upload_raw = db.IntegerProperty(default=0)
  # 2. LAN
  lan_download = db.IntegerProperty()
  lan_download_raw = db.IntegerProperty(default=0)
  lan_upload = db.IntegerProperty()
  lan_upload_raw = db.IntegerProperty(default=0)
  # 3. WLAN (internet)
  wlan_download = db.IntegerProperty()
  wlan_download_raw = db.IntegerProperty(default=0)
  wlan_upload = db.IntegerProperty()
  wlan_upload_raw = db.IntegerProperty(default=0)

  # ADSL metrics
  snr_margin_up = db.FloatProperty()
  snr_margin_down = db.FloatProperty()
  attenuation_up = db.FloatProperty()
  attenuation_down = db.FloatProperty()

  def _SetCounterMetric(self, metric, value, previous):
    if previous and value >= getattr(previous, metric + '_raw'):
      setattr(self, metric, value - getattr(previous, metric + '_raw'))
    else:
      setattr(self, metric, value)
    setattr(self, metric + '_raw', value)

  @classmethod
  def MakeSnapshot(cls, parent):
    api = router_api.HTTPRouterApi(parent.address, parent.admin_login, parent.admin_password)

    now = datetime.datetime.now()
    snapshot_minute = now.minute / parent.pull_interval * parent.pull_interval
    snapshot = now - datetime.timedelta(minutes=now.minute - snapshot_minute)
    snapshot_key = snapshot.strftime('%Y%m%d%H%M')

    previous_snapshot = snapshot - datetime.timedelta(minutes=parent.pull_interval)
    previous_snapshot_key = previous_snapshot.strftime('%Y%m%d%H%M')

    previous = cls.get_by_key_name(previous_snapshot_key, parent)
    current = cls(parent=parent, key_name=snapshot_key)

    # wan traffic
    try:
      stats = api.GetWanStats()
    except router_api.UnreachableError:
      return False

    d = stats['download']['Bytes']
    u = stats['upload']['Bytes']
    current._SetCounterMetric('wan_download', d, previous)
    current._SetCounterMetric('wan_upload', u, previous)
    if d == cls.COUNTER_MAX_VALUE or u == cls.COUNTER_MAX_VALUE:
      api.ResetWanStats()

    # lan/wlan traffic
    try:
      stats = api.GetLanStats()
    except router_api.UnreachableError:
      return False

    # update lan download counter
    ld = stats['Ethernet']['download']['Bytes']
    lu = stats['Ethernet']['upload']['Bytes']
    wd = stats['Wireless']['download']['Bytes']
    wu = stats['Wireless']['upload']['Bytes']
    current._SetCounterMetric('lan_download', ld, previous)
    current._SetCounterMetric('lan_upload', lu, previous)
    current._SetCounterMetric('wlan_download', wd, previous)
    current._SetCounterMetric('wlan_upload', wu, previous)
    if (ld == cls.COUNTER_MAX_VALUE or lu == cls.COUNTER_MAX_VALUE or
        wd == cls.COUNTER_MAX_VALUE or wu == cls.COUNTER_MAX_VALUE):
      api.ResetLanStats()

    # lan/wlan traffic
    try:
      stats = api.GetAdslStats()
    except router_api.UnreachableError:
      return False

    current.snr_margin_down = stats['SNR Margin']['down']
    current.snr_margin_up = stats['SNR Margin']['up']
    current.attenuation_down = stats['Attenuation']['down']
    current.attenuation_up = stats['Attenuation']['up']

    current.put()
    return True


UNITS = ['B', 'KiB', 'MiB', 'GiB', 'KB', 'MB', 'GB']

def toUnit(num, unit):
  num = float(num)
  if unit not in UNITS:
    return num
  else:
    idx = UNITS.index(unit)
    if idx < 4:
      return num / 2**(10*idx)
    else:
      idx = idx - 3
      return num / 10**(3*idx)


def toString(num):
  s = []
  num = str(num)
  l, last = divmod(len(num), 3)
  idx = 0
  if last:
    s.append(num[:last])
    idx = last
  for i in range(l):
    s.append(num[idx:idx+3])
    idx = idx + 3
  return ' '.join(s)


class MainPage(webapp.RequestHandler):
  """ Chart drawing.
  """
  INTERVALS = 5

  def get(self):
    user = users.get_current_user()
    if user:
      routers = Router.all()
      routers.filter("owner =", user)
      routers = list(routers)
      if len(routers) == 0:
        self.redirect('/add')
        return
    else:
      self.redirect(users.create_login_url('/'))
      return

    info = []

    now = datetime.datetime.now()
    for router in routers:
      # calculate last snapshot time
      last_snapshot_minute = now.minute / router.pull_interval * router.pull_interval
      last_snapshot = now - datetime.timedelta(minutes=now.minute - last_snapshot_minute)
      last_snapshot_key = last_snapshot.strftime('%Y%m%d%H%M')

      metrics = Metrics.get_by_key_name(last_snapshot_key, router)
      if metrics:
        chart_url = self.getChartURL([
            metrics.wan_download,
            metrics.lan_download,
            metrics.wlan_download,
            metrics.wan_upload,
            metrics.lan_upload,
            metrics.wlan_upload
        ])
      else:
        chart_url = None
      info.append({
          'key': router.key(),
          'address': router.address,
          'chart_url': chart_url,
          'updated': router.modified,
          'created': router.created,
          'working': router.working
      })

    template_values = {
        'info': info,
    }
    path = os.path.join(os.path.dirname(__file__), 'index.html')
    self.response.out.write(template.render(path, template_values))

  def getChartURL(self, amounts):
    # find maximum value and best unit for it
    max_amount = max(amounts)
    unit = self.getUnit(max_amount)
    # convert values to that unit and find max amount onesmore 
    amounts = [toUnit(i, unit) for i in amounts]
    max_amount = max(amounts)
    # find smallest integer value larger than maximum that will be used 
    # for data scalling
    scale = math.ceil(max_amount / self.INTERVALS) * self.INTERVALS
    # caculate interval size for grid lines (there will be 6 intevals)
    interval = 100.0 / self.INTERVALS
    # prepare data sets
    download_data_set = ['%.2f' % i for i in amounts[:3]]
    download_data_set = ','.join(download_data_set)
    upload_data_set = ['%.2f' % i for i in amounts[3:]]
    upload_data_set = ','.join(upload_data_set)
    # prepare url
    url = 'http://chart.apis.google.com/chart?'
    query = {
        'cht': 'bhg',
        'chs': '550x200',
        'chd': 't:%s|%s' % (download_data_set, upload_data_set),
        'chds': '0,%d' % scale,
        'chco': '4D89F9,C6D9FD',
        'chdl': 'Download|Upload',
        'chdlp': 'b',
        'chxt': 'x,y',
        'chxl': '1:|WLAN|LAN|WAN',
        'chxr': '0,0,%d,%d' % (scale, scale/self.INTERVALS),
        'chxs': '1,000000,12,0',
        'chbh': 'a,0,10',
        'chg': '%.2f,0,5,5' % interval,
        'chm': 'N*f2*+%s,0000dd,0,-1,12|N*f2*+%s,0000dd,1,-1,12' % (unit, unit)
    }
    url += '&'.join(['%s=%s' % (k, v) for k, v in query.items()])
    return cgi.escape(url)

  def getUnit(self, amount):
    if amount < 1024:
      return 'B'
    if amount < 1048576:
      return 'KiB'
    if amount < 1073741824:
      return 'MiB'
    return 'GiB'


class Add(webapp.RequestHandler):

  def get(self):
    user = users.get_current_user()
    if not user:
      self.redirect(users.create_login_url('/add'))
      return
    path = os.path.join(os.path.dirname(__file__), 'add.html')
    self.response.out.write(template.render(path, {}))

  def post(self):
    user = users.get_current_user()
    if not user:
      self.redirect(users.create_login_url('/add'))
      return

    address = self.request.get('address')
    admin_login = self.request.get('login')
    admin_password = self.request.get('password')
    router = Router(
        owner=user,
        address=address,
        admin_login=admin_login,
        admin_password=admin_password)
    router.put()
    self.redirect('/')


class Delete(webapp.RequestHandler):

  def get(self):
    user = users.get_current_user()
    if not user:
      self.redirect(users.create_login_url('/'))
      return
    key = self.request.get('key')
    if key:
      obj = db.get(db.Key(key))
      obj.delete()
    self.redirect('/')


application = webapp.WSGIApplication([
    ('/', MainPage),
    ('/add', Add),
    ('/delete', Delete)
], debug=True)


def main():
  run_wsgi_app(application)


if __name__ == "__main__":
  main()
